#include <alloca.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <errno.h>
#include <assert.h>

#include "logging.h"

static const char * tags[LV_NUMS] = {
										"[FATAL]", /* LV_FATAL */
										"[ERROR]", /* LV_ERROR */
										"[SYSTM]", /* LV_SYS */
										"[WARNG]", /* LV_WARN */
										"[INFOM]", /* LV_INFO */
										"[DEBUG]", /* LV_DEBUG */
									};

int logging_free(LOGGING * p)
{
	if (!p) return 1;

	if (p->fd == 0 || p->fd == STDOUT_FILENO || p->fd == STDERR_FILENO) goto OUT;

	struct flock lock = {F_UNLCK, SEEK_SET, 0, 0};
	if (fcntl(p->fd, F_SETLK, &lock) < 0)
		fprintf(stderr, "unlock %s: %s\n", p->path, strerror(errno));

	close(p->fd);
OUT:
	p->fd = 0;
	p->path[0] = '\0';
	free(p);
	return 0;
}

LOGGING * logging_new(const char * path, unsigned int level, unsigned int bufsz)
{
	if (level >= LV_NUMS || !path || strlen(path) > LOG_PATH_SIZE - 1) return NULL;

	LOGGING * p = malloc(sizeof(struct LOGGING));
	if (!p) return NULL;

	p->level = level;
	p->bufsz = bufsz;
	if (p->bufsz < LOG_BUFFER_SIZE) p->bufsz = LOG_BUFFER_SIZE;

	p->fd = 0;
	if (!strcasecmp(path, "stdout"))
		p->fd = STDOUT_FILENO;
	else if (!strcasecmp(path, "stderr"))
		p->fd = STDERR_FILENO;

	if (p->fd > 0) goto OUT;

	if ((p->fd = open(path, O_CREAT|O_WRONLY|O_APPEND, 0644)) == -1) {
		fprintf(stderr, "open %s: %s\n", path, strerror(errno));
		free(p); 
		return NULL;
	}
	struct flock lock = {F_WRLCK, SEEK_SET, 0, 0};

	if (fcntl(p->fd, F_SETLK, &lock) == -1) {
		fprintf(stderr, "lock %s: %s\n", path, strerror(errno));
		free(p); 
		return NULL;
	}

OUT:
	strncpy(p->path, path, LOG_PATH_SIZE - 1);
	return p;
}

static __inline__ int _lvok(unsigned int logging_level, unsigned int user_level)
{
	return (user_level <= logging_level);
}

static __inline__ void _rlogit(int fd, unsigned int level, const char * msg, int len)
{
	assert(fd && level < LV_NUMS && msg);

	time_t t = time(NULL);
	struct tm tm;
	localtime_r(&t, &tm);

	char label[LOG_LABEL_SIZE];
	int len2 = snprintf(label, LOG_LABEL_SIZE, "%s[%.2d:%.2d:%.2d %.2d/%.2d]",
			tags[level], tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_mon+1, tm.tm_mday);

	if (write(fd, label, len2) != len2 || write(fd, msg, len) != len 
			|| write(fd, "\n", 1) !=1)
		fprintf(stderr, "write %d: %s\n", fd, strerror(errno));
}

static __inline__ void _vlogit(int fd, int bufsz, unsigned int level, 
								const char * fmt, va_list argp)
{
	assert(fd && bufsz && level < LV_NUMS && argp);

	char * msg = alloca(bufsz);
	int len = vsnprintf(msg, bufsz - 1, fmt, argp);
	msg[len] = '\0';

	_rlogit(fd, level, msg, len);
}


void logit(LOGGING * p, unsigned int level, const char * fmt, ...)
{
	assert(fmt);

	if (!p) return;

	if (!_lvok(p->level, level)) return;

	va_list argp;
	va_start(argp, fmt);

	_vlogit(p->fd, p->bufsz, level, fmt, argp);

	va_end(argp);
}

void rlogit(LOGGING *p, unsigned int level, const char * msg)
{
	if (!p) return;

	if (!_lvok(p->level, level)) return;

	_rlogit(p->fd, level, msg, strlen(msg));
}

void vlogit(LOGGING * p, unsigned int level, const char * fmt, va_list argp)
{
	if (!p) return;

	if (!_lvok(p->level, level)) return;

	_vlogit(p->fd, p->bufsz, level,  fmt,  argp);
}

int lvok(LOGGING *p, unsigned int level)
{
	if (!p) return 0;

	return _lvok(p->level, level);
}

#ifdef LOGGING_MAIN
int main()
{
	LOGGING * p = logging_new("test.log", LV_DEBUG, 1024);
	logit(p, LV_SYS, "[%d]%s", p->level, p->path);
	logging_free(p);
	return 0;
}
#endif
